package com.basker.pisces.orm.query;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang3.ArrayUtils;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import com.basker.pisces.core.PageRowList;
import com.basker.pisces.core.RowContract;
import com.basker.pisces.core.enums.TableType;
import com.basker.pisces.core.meta.row.RowContractMeta;
import com.basker.pisces.core.query.FetchMode;
import com.basker.pisces.core.query.GroupField;
import com.basker.pisces.core.query.IQuery;
import com.basker.pisces.core.query.OrderField;
import com.basker.pisces.core.query.QueryInfo;
import com.basker.pisces.core.query.SelectField;
import com.basker.pisces.core.query.criteria.Criterion;
import com.basker.pisces.core.query.criteria.iterate.CriteriaIterator;
import com.basker.pisces.core.query.criteria.iterate.CriterionIterateArgs;
import com.basker.pisces.core.query.criteria.iterate.ICriteriaIterateHandler;
import com.basker.pisces.core.query.meta.FieldDesc;
import com.basker.pisces.core.query.meta.IReferenceFieldDesc;
import com.basker.pisces.core.query.meta.QueryMeta;
import com.basker.pisces.core.query.meta.TableDesc;
import com.basker.pisces.exception.ExceptionFactory;
import com.basker.pisces.orm.session.ISqlSession;
import com.basker.pisces.orm.session.SqlSessionFactoryManager;
import com.basker.pisces.utils.PageUtils;

public class DefaultQuery implements IQuery {

    private static final Object[] EMPTY_ARGS = ArrayUtils.EMPTY_OBJECT_ARRAY;
    private QueryMeta queryMeta;
    private List<TableDesc> joinTables = new ArrayList<TableDesc>();

    private Set<String> joinedTables = new HashSet<String>(8);

    public DefaultQuery(QueryMeta queryMeta) {
        this.queryMeta = queryMeta;
    }

    @Override
    public PageRowList queryForPageRows(QueryInfo queryInfo, PagingInfo pagingInfo) {
        Assert.isTrue(pagingInfo.isPagingQuery(), "parameter 'pagingInfo' is illegal");

        return (PageRowList) this.innerQueryForRows(queryInfo, pagingInfo);
    }

    @Override
    public int queryForRowCount(QueryInfo queryInfo) {
        Assert.notNull(queryInfo, "paramter 'queryInfo' is required");

        String countSql = this.genCountSql(queryInfo);
        Object[] args = getQueryArguments(queryInfo.getCriteria());

        try (ISqlSession sqlSession = this.createSqlSession(this.queryMeta.getSchema())) {
            Integer count = sqlSession.queryForObject(Integer.class, countSql, args);
            return count != null ? count : 0;
        } catch (Exception e) {
            throw ExceptionFactory.createRuntimeException(e);
        }
    }

    @Override
    public List<RowContract> queryForRows(QueryInfo queryInfo) {
        Assert.notNull(queryInfo, "paramter 'queryInfo' is required");

        return this.innerQueryForRows(queryInfo, PagingInfo.NO_PAGING);
    }

    private void addJoinTable(TableDesc table) {
        if (this.joinTables.contains(table)) {
            return;
        }

        TableDesc parentTable = table.getParentTable();
        if (parentTable != null) {
            this.addJoinTable(parentTable);
        }

        this.joinTables.add(table);
    }

    private void analysisCriteria(List<Criterion> criteria) {
        if (CollectionUtils.isEmpty(criteria)) {
            return;
        }

        CriteriaIterator iterator = new CriteriaIterator(this.queryMeta);
        iterator.iterate(criteria, new CriteriaAnalyzer());
    }

    /**
     * 分析分组字段，如果字段是关联表的字段，那么需要将关联表添加到join表中
     *
     * @param orderFields
     */
    private void analysisGroupFields(List<GroupField> groupFields) {
        if (CollectionUtils.isEmpty(groupFields)) {
            return;
        }

        for (GroupField groupField : groupFields) {
            FieldDesc field = this.queryMeta.getField(groupField.getFieldId());
            if (field == null) {
                continue;
            }

            TableDesc table = field.getHostTable();

            // while (field instanceof IReferenceFieldDesc) {
            // IReferenceFieldDesc refField = (IReferenceFieldDesc) field;
            //
            // field = refField.getRefField();
            // if (field != null) {
            // table = field.getHostTable();
            // } else {
            // break;
            // }
            // }

            if (table != null) {
                this.addJoinTable(table);
            }
        }

    }

    private void analysisOrderFields(List<OrderField> orderFields) {
        if (CollectionUtils.isEmpty(orderFields)) {
            return;
        }

        for (OrderField orderField : orderFields) {
            FieldDesc field = this.queryMeta.getField(orderField.getFieldId());
            if (field == null) {
                continue;
            }

            TableDesc table = field.getHostTable();

            while (field instanceof IReferenceFieldDesc) {
                // 关联字段使用其关联表的业务字段排序，因为用主键排序没有意义
                IReferenceFieldDesc refField = (IReferenceFieldDesc) field;

                field = refField.getReferencedCodeField();
                if (field != null) {
                    table = field.getHostTable();
                } else {
                    break;
                }
            }

            if (table != null) {
                this.addJoinTable(table);
            }
        }
    }

    private void analysisSelectField(List<SelectField> selectFields, FetchMode fetchMode) {
        if (CollectionUtils.isEmpty(selectFields)) {
            return;
        }

        for (SelectField selectField : selectFields) {
            String fieldId = selectField.getFieldId();
            FieldDesc fieldDesc = this.queryMeta.getField(fieldId);

            if (fieldDesc == null) {
                throw new RuntimeException(
                        "select field '" + fieldId + "' is not found in queryMeta '" + this.queryMeta.toString() + "'");
            }

            if (this.ignoreReferenceSelectField(fieldDesc, fetchMode)) {
                // select模式下，引用表不参与联表
                continue;
            }

            this.addJoinTable(fieldDesc.getHostTable());
        }
    }

    private void anlyseSelectTables(QueryInfo queryInfo, boolean ignoreSelect) {
        this.joinTables.clear();
        this.joinedTables.clear();

        if (!ignoreSelect) {
            this.analysisSelectField(queryInfo.resolveSelectFields(this.queryMeta),
                    queryInfo.getReferenceSelectFieldFetchMode());
        }
        this.analysisCriteria(queryInfo.getCriteria());
        this.analysisGroupFields(queryInfo.getGroupFields());
        this.analysisOrderFields(queryInfo.getOrderFields());
    }

    private void appendJoinClauses(StringBuilder from, TableDesc joinTable, TableDesc preTable) {
        from.append(MessageFormat.format(" LEFT JOIN {0} {1} ON", joinTable.getName(), joinTable.getAlias()));

        String preTableFullFieldName = null;
        String joinTableFullFiledName = null;

        TableType tableType = joinTable.getType();

        if (TableType.Sub.equals(tableType) || TableType.Child.equals(tableType)) {
            preTableFullFieldName = preTable.getPkFullFieldName();
            joinTableFullFiledName = joinTable.getFkFullFieldName();
        } else if (TableType.Reference.equals(tableType)) {
            preTableFullFieldName = preTable.getFullFieldName(joinTable.getFkFieldName());
            joinTableFullFiledName = joinTable.getPkFullFieldName();
        }

        from.append(MessageFormat.format(" ({0} = {1}) ", preTableFullFieldName, joinTableFullFiledName));
    }

    private ISqlSession createSqlSession(String dataSourceName) {
        return SqlSessionFactoryManager.getDfaultFactory().createSession(this.queryMeta.getSchema());
    }

    private String genCountSql(QueryInfo queryInfo) {
        this.anlyseSelectTables(queryInfo, true);

        String fromClauses = this.genFromClauses();
        String selectCountClauses = "COUNT(1) AS TOTALCOUNT";
        String whereClauses = this.getWhereClauses(queryInfo.getCriteria());
        String groupByClauses = this.getGroupByClauses(queryInfo.getGroupFields());

        String countSql = String.join(" ", "SELECT", selectCountClauses, "FROM", fromClauses, whereClauses,
                groupByClauses);

        if (queryInfo.isGroupQuery()) {
            return String.join(" ", "SELECT", selectCountClauses, "FORM(", countSql, ")");
        } else {
            return countSql;
        }
    }

    private String genFromClauses() {
        int size = joinTables.size();
        StringBuilder fromClauses = new StringBuilder(size * 60);

        if (size == 0) {
            // 不指定连表 则主表默认需要查询
            joinTables.add(this.queryMeta.getMainTable());
        }

        TableDesc tableDesc = joinTables.get(0);
        fromClauses.append(tableDesc.getName() + " " + tableDesc.getAlias());
        this.joinedTables.add(tableDesc.getAlias());

        for (int i = 1; i < this.joinTables.size(); i++) {
            TableDesc joinTable = this.joinTables.get(i);
            joinTable(fromClauses, joinTable);
        }

        return fromClauses.toString();
    }

    private String genListSQL(QueryInfo queryInfo) {
        this.anlyseSelectTables(queryInfo, false);

        String fromClauses = this.genFromClauses();
        String selectFieldClauses = this.getSelectClauses(queryInfo.resolveSelectFields(this.queryMeta),
                queryInfo.getReferenceSelectFieldFetchMode());
        String whereClauses = this.getWhereClauses(queryInfo.getCriteria());
        String groupByClauses = this.getGroupByClauses(queryInfo.getGroupFields());
        String orderClauses = this.getOrderClauses(queryInfo.getOrderFields());

        return String.join(" ", "SELECT", selectFieldClauses, "FROM", fromClauses, whereClauses, groupByClauses,
                orderClauses);
    }

    private String getGroupByClauses(List<GroupField> groupFields) {
        if (CollectionUtils.isEmpty(groupFields)) {
            return "";
        }

        int len = groupFields.size();
        StringBuilder groupByClauses = null;

        for (GroupField groupField : groupFields) {
            FieldDesc field = this.queryMeta.getField(groupField.getFieldId());

            if (field == null) {
                continue;
            }

            if (groupByClauses == null) {
                groupByClauses = new StringBuilder(len * 20);
                groupByClauses.append("GROUP BY ");
            }

            String groupByExp = field.getGroupFieldName(groupField);
            groupByClauses.append(groupByExp);
            groupByClauses.append(",");
        }

        return groupByClauses != null ? groupByClauses.deleteCharAt(groupByClauses.length() - 1).toString() : "";
    }

    private String getOrderClauses(List<OrderField> orderFields) {
        if (CollectionUtils.isEmpty(orderFields)) {
            return "";
        }

        StringBuilder orderBy = null;

        int len = orderFields.size();
        for (int i = 0; i < len; i++) {
            OrderField orderField = orderFields.get(i);
            FieldDesc field = this.queryMeta.getField(orderField.getFieldId());

            if (field == null) {
                continue;
            }

            if (orderBy == null) {
                orderBy = new StringBuilder(len * 20);
                orderBy.append("ORDER BY ");
            }

            String orderExp = field.getOrderFieldName(orderField);
            orderBy.append(orderExp);

            if (i != len - 1) {
                orderBy.append(",");
            }
        }

        return orderBy != null ? orderBy.toString() : "";
    }

    private Object[] getQueryArguments(List<Criterion> criteria) {
        if (CollectionUtils.isEmpty(criteria)) {
            return EMPTY_ARGS;
        }

        return QueryParameterValueGenerator.genArgumentValue(this.queryMeta, criteria);
    }

    private String getSelectClauses(List<SelectField> selectFields, FetchMode fetchMode) {
        StringBuilder selectClauses = new StringBuilder();

        for (SelectField selectField : selectFields) {
            FieldDesc fieldDesc = this.queryMeta.getField(selectField.getFieldId());
            if (fieldDesc == null) {
                continue;
            }

            if (this.ignoreReferenceSelectField(fieldDesc, fetchMode)) {
                // select模式下，不直接查询字段
                continue;
            }

            String selectFieldName = fieldDesc.getSelectFieldName(selectField);
            selectClauses.append(selectFieldName).append(",");
        }

        if (selectClauses.length() > 0) {
            selectClauses.deleteCharAt(selectClauses.length() - 1);
        }

        return selectClauses.toString();
    }

    private String getWhereClauses(List<Criterion> criteria) {
        if (CollectionUtils.isEmpty(criteria)) {
            return "";
        }

        String whereClauses = WhereClausesGenerator.genWhereClauses(this.queryMeta, criteria);

        return String.join(" ", "WHERE", whereClauses);
    }

    private boolean ignoreReferenceSelectField(FieldDesc fieldDesc, FetchMode fetchMode) {
        return fieldDesc.getHostTable().isReferenceTable() && FetchMode.SELECT.equals(fetchMode);
    }

    private List<RowContract> innerQueryForRows(QueryInfo queryInfo, PagingInfo pagingInfo) {
        try (ISqlSession sqlSession = this.createSqlSession(this.queryMeta.getSchema())) {

            RowContractMeta rowMeta = this.queryMeta.getRowContractMeta(queryInfo);
            Object[] args = getQueryArguments(queryInfo.getCriteria());

            if (pagingInfo.isPagingQuery()) {
                String countSql = this.genCountSql(queryInfo);
                Integer totalCount = sqlSession.queryForObject(Integer.class, countSql, args);
                totalCount = totalCount != null ? totalCount : 0;

                int pageSize = pagingInfo.getPageSize();
                int pageIndex = Math.min(Math.max(PagingInfo.FIRST_PAGE_INDEX, pagingInfo.getPageIndex()),
                        PageUtils.getTotalPageCount(totalCount, pageSize));

                List<RowContract> rows = null;
                if (totalCount == 0) {
                    rows = Collections.emptyList();
                } else {
                    String sql = this.genListSQL(queryInfo);
                    rows = sqlSession.queryForPageRows(rowMeta, sql, pageIndex, pageSize, args);
                }

                return new PageRowList(rows, totalCount, pageIndex, pageSize);
            } else {
                String sql = this.genListSQL(queryInfo);
                return sqlSession.queryForRows(rowMeta, sql, args);
            }
        } catch (Exception e) {
            throw ExceptionFactory.createRuntimeException(e);
        }
    }

    private void joinTable(StringBuilder joinClauses, TableDesc joinTable) {
        TableDesc preTableDesc = joinTable.getParentTable();
        if (preTableDesc == null) {
            return;
        }

        if (this.joinedTables.contains(joinTable.getAlias())) {
            return;
        }

        appendJoinClauses(joinClauses, joinTable, preTableDesc);

        this.joinedTables.add(joinTable.getAlias());
    }

    class CriteriaAnalyzer implements ICriteriaIterateHandler {
        @Override
        public void beginIterate(CriterionIterateArgs args) {
            TableDesc hostTable = args.getHostTable();
            if (hostTable == null) {
                return;
            }

            Criterion criterion = args.getCriterion();

            // 如果条件所属表是子表 则需要exists
            if (analysisHostTableIsExist(hostTable)) {
                criterion.setExist(true);
            }

            if (!criterion.isExist()) {
                DefaultQuery.this.addJoinTable(hostTable);
            } else {
                // 获取最近的连表，如果存在，那么当前条件可以无须拼接子查询，或者减少子查询的层数
                TableDesc joinedTable = this.findJoinedTable(hostTable);
                if (joinedTable == null) {
                    return;
                }

                // 1.当前表与连表一致，不需要子查询，因为字段所在表已经参与连表
                // 2.当前表与连表同一层级，可以将当前表一起连表，也不需要子查询
                if (joinedTable == hostTable || joinedTable.getLevel() == hostTable.getLevel()) {
                    criterion.setExist(false);

                    DefaultQuery.this.addJoinTable(hostTable);
                } else {
                    // 3.连表比当前表层级高，所以子查询不可完全去除，但是可以指定条件的子查询拼接到连表为止
                    criterion.setExistRootTableName(joinedTable.getName());

                    DefaultQuery.this.addJoinTable(joinedTable);
                }
            }
        }

        private boolean analysisHostTableIsExist(TableDesc hostTable) {
            if (TableType.Main.equals(hostTable.getType())) {
                return false;
            }

            while (true) {
                if (TableType.Child.equals(hostTable.getType())) {
                    return true;
                }

                hostTable = hostTable.getParentTable();
                if (hostTable == null) {
                    return false;
                }
            }
        }

        /**
         * 查询当前的左联表集合中，距离hostTable最近的子表
         *
         * @param hostTable
         * @return
         */
        private TableDesc findJoinedTable(TableDesc hostTable) {
            TableDesc tableDesc = hostTable;
            while (true) {
                if (DefaultQuery.this.joinTables.contains(tableDesc)) {
                    return tableDesc;
                }

                tableDesc = tableDesc.getParentTable();

                if (TableType.Reference.equals(tableDesc.getType())) {
                    return tableDesc;
                }

                if (TableType.Main.equals(tableDesc.getType())) {
                    return null;
                }
            }
        }
    }

}
